package com.cy.dda.service.wechat.impl;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

import com.cy.dda.dao.mongodb.MongoDao;
import org.apache.commons.codec.digest.DigestUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import com.cy.dda.comm.constant.CacheConstants;
import com.cy.dda.comm.utils.CommonUtils;
import com.cy.dda.comm.utils.JsonObjectMapper;
import com.cy.dda.comm.utils.SpringContextUtils;
import com.cy.dda.dao.mongodb.WechatMessageTemplateLogDao;
import com.cy.dda.model.Result;
import com.cy.dda.model.doc.WechatMessageTemplateLog;
import com.cy.dda.model.domain.user.UserWechat;
import com.cy.dda.model.dto.wechat.WechatMessageTemplateDTO;
import com.cy.dda.model.dto.wechat.WechatMessageTemplateDataDTO;
import com.cy.dda.model.enums.wechat.WechatMessageTemplate;
import com.cy.dda.model.enums.wechat.WechatSopce;
import com.cy.dda.service.BaseService;
import com.cy.dda.service.user.UserWechatService;
import com.cy.dda.service.wechat.WechatService;

import lombok.extern.slf4j.Slf4j;

/**
 * 微信接口服务 WechatService
 *
 * @author: tanyilin
 * @create: 2018年11月20日 下午5:36:00
 */
@Slf4j
@Service
public class WechatServiceImpl extends BaseService implements WechatService {
	
	@Autowired
    private RedisTemplate redisTemplate;
    @Autowired
    private RestTemplate restTemplate;
    @Autowired
    private UserWechatService userWechatService;
    @Autowired
    private WechatMessageTemplateLogDao wechatMessageTemplateLogDao;
    @Autowired
    private MongoDao mongoDao;
	
    /** appId */
    @Value("${wechat.app-id}")
    private String appId;
    
    /** secret */
    @Value("${wechat.secret}")
    private String secret;
    
    /** 获取基础 access-token 的url */
    @Value("${wecaht.access-token.url}")
    private String accessTokenUrl;
    
    /** 获取微信分享 ticket 的url */
    @Value("${wechat.ticket.url}")
    private String ticketUrl;
    
    /** 用户授权的url */
    @Value("${wechat.oauth2.authorize.url}")
    private String authorizeUrl;
    
    /** 获取用户 access-token 的url */
    @Value("${wechat.oauth2.user-access-token.url}")
    private String userAccessTokenUrl;
    
    /** 获取微信用户信息的url */
    @Value("${wechat.oauth2.user-info.url}")
    private String userInfoUrl;
    
    /** 用户授权后回调的绑定微信关系的入口url */
    @Value("${wechat.oauth2.bind.callback.url}")
    private String bindCallbackUrl;
    
    /** 发送微信消息模板的url */
    @Value("${wechat.message.template.send.url}")
    private String messageTemplateSendUrl;
    
    /** 微信分享参数 */
    private final String shareKey = "jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s";

    /** redis key */
    private final String authKey = "WECHAT_AUTH_KEY:";

	@Override
	@Cacheable(value = CacheConstants.CACHE_VALUE_1H)
	public String getAccessToken() {
		
		//组装参数
		Map<String, String> params = new HashMap<>();
		params.put("appid", appId);
        params.put("secret", secret);

        //发送请求获取token
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(accessTokenUrl, String.class, params);
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "获取TOKEN失败");
        Map<String, String> map = JsonObjectMapper.read(responseEntity.getBody(), Map.class);
        log.info("请求微信access_token回调: {}", map);
        String token = map.get("access_token");
        state(StringUtils.hasLength(token), "获取TOKEN失败");
        return token;
	}

	@Override
	@Cacheable(value = CacheConstants.CACHE_VALUE_1H)
	public String getTicket(String token) {
		
		//组装参数
		Map<String, String> params = new HashMap<>();
		params.put("token", token);
		
		//发送请求获取token
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(ticketUrl, String.class, params);
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "获取TICKET失败");
        Map<String, String> map = JsonObjectMapper.read(responseEntity.getBody(), Map.class);
        log.info("请求ticket回调: {}", map);
        String ticket = map.get("ticket");
        state(StringUtils.hasLength(ticket), "获取TICKET失败");
        return ticket;
	}

	@Override
	public Map<String, String> getShareSignature(String token, String ticket, String url) {

		String noncestr = UUID.randomUUID().toString();
        String timestamp = System.currentTimeMillis() + "";
        timestamp = timestamp.substring(0, 10);
		
        String sign = String.format(shareKey, ticket, noncestr, timestamp, url);
        String signature = DigestUtils.sha1Hex(sign);
        Map<String, String> map = new HashMap<>();
        map.put("appId", appId);
        map.put("timestamp", timestamp);
        map.put("nonceStr", noncestr);
        map.put("signature", signature);
        map.put("url", url);
        log.info("微信分享签名====================》 token  =  {}", token);
        log.info("微信分享签名====================》 ticket =  {}", ticket);
        log.info("微信分享签名====================》 {}", map);
        return map;
	}

	@Override
	public Result sendTemplateMessage(WechatMessageTemplateDTO wechatMessageTemplateDTO) {
		
		String openId = wechatMessageTemplateDTO.getOpneId();
		String json = JsonObjectMapper.toJson(wechatMessageTemplateDTO);
        
        //记录微信消息发送日志
        WechatMessageTemplateLog requestLog = new WechatMessageTemplateLog();
        requestLog.setOpenId(wechatMessageTemplateDTO.getOpneId());
        requestLog.setRequestData(json);
        requestLog.setCreateTime(new Date());
        wechatMessageTemplateLogDao.insert(requestLog);
        
        //发送微信模板消息
        log.info("给用户【{}】发送微信消息模版请求报文 requestData： {}", openId, json);
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON_UTF8);
        HttpEntity<String> httpEntity = new HttpEntity<>(json, headers);
        //获取微信accessToken
        String accessToken = SpringContextUtils.getBean(WechatService.class).getAccessToken();
        ResponseEntity<String> responseEntity = restTemplate.postForEntity(messageTemplateSendUrl, httpEntity, String.class, accessToken);
        
		if(!HttpStatus.OK.equals(responseEntity.getStatusCode())) {
			return Result.fail("发送微信消息模版请求失败");
		}
		
		log.info("给用户【{}】发送微信消息模版回调报文 responseData： {}", openId, responseEntity.getBody());
		
		//保存回调记录
		WechatMessageTemplateLog responseLog = new WechatMessageTemplateLog();
        responseLog.setId(requestLog.getId());
		responseLog.setResponseData(responseEntity.getBody());
		responseLog.setUpdateTime(new Date());
        mongoDao.update(responseLog);
		
		String responseBody = responseEntity.getBody();
		Map<String, Object> result = JsonObjectMapper.read(responseBody, Map.class);
		Integer errcode = (Integer) result.get("errcode");
        if (!Integer.valueOf(0).equals(errcode)) {
            return Result.fail("发送微信消息模版失败");
        }
        
		return Result.success();
	}

	@Override
	public String getAuthorizeUrl(Long userId, WechatSopce wechatSopce) {
		
		//生成随机key
		String state = UUID.randomUUID().toString();
		String url = String.format(authorizeUrl, appId, bindCallbackUrl, wechatSopce.getSopce(), state);
		
		Map<String, String> map = new HashMap<String, String>();
		map.put("userId", userId.toString());
		map.put("wechatSopce", wechatSopce.name());
		
		String key = authKey.concat(state);
		
		redisTemplate.opsForHash().putAll(key, map);
		redisTemplate.expire(key, 4, TimeUnit.MINUTES);
		
		log.info("用户微信授权地址  url = {} ", url);
		
		return url;
	}

	@Override
	public Map<String, Object> getUserAccessToken(String code) {
		
		//组装参数
		Map<String, String> params = new HashMap<>();
		params.put("appid", appId);
		params.put("secret", secret);
		params.put("code", code);
        
        //发送请求
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(userAccessTokenUrl, String.class, params);
        
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "请求微信用户的ACCESSTOKEN失败");
        log.info("获取微信用户ACCESSTOKEN回调: {}", responseEntity.getBody());
        
        Map<String, Object> body = JsonObjectMapper.read(responseEntity.getBody(), Map.class);
        Object errorCode = body.get("errcode");
        state(errorCode == null, "获取微信用户的ACCESSTOKEN失败");
        
		return body;
	}

	@Override
	public Map<String, Object> getUserInfo(String accessToken, String openId) {
		
		//组装参数
		Map<String, String> params = new HashMap<>();
		params.put("access_token", accessToken);
		params.put("openid", openId);
        
        //发送请求
        ResponseEntity<String> responseEntity = restTemplate.getForEntity(userInfoUrl, String.class, params);
        
        state(HttpStatus.OK.equals(responseEntity.getStatusCode()), "请求获取微信用户信息失败");
        log.info("请求获取微信用户信息回调: {}", responseEntity.getBody());
        
        Map<String, Object> body = JsonObjectMapper.read(responseEntity.getBody(), Map.class);
		Object errorCode = body.get("errcode");
		state(errorCode == null, "获取微信用户信息失败");
        
		return body;
	}

	@Override
	public UserWechat bindCallback(String code, String state) {
		
		//缓存
		String key = authKey.concat(state);
		Map<String, String> map = redisTemplate.opsForHash().entries(key);
		state(!CommonUtils.isEmpty(map), "验证超时，请重新操作");
		
		//获得缓存中的信息
		Long userId = Long.valueOf(map.get("userId"));
		WechatSopce wechatSopce = WechatSopce.get(map.get("wechatSopce"));
		log.info("缓存中的信息：{} ", map);
		
		//获取微信用户accessToken
        Map<String, Object> body = getUserAccessToken(code);
        String accessToken = body.get("access_token").toString();
        String openId = body.get("openid").toString();
        log.info("微信用户accessToken：{} ", body);
        
        //获取用户用户信息
        Object unionId = body.get("unionid");
        Object nickName = body.get("nickname");
        Object headImg = body.get("headimgurl");
        
        if(WechatSopce.USER_INFO.equals(wechatSopce)) {
        	//获取微信用户信息
        	body = getUserInfo(accessToken, openId);
        	log.info("微信用户信息：{} ", body);
        	unionId = body.get("unionid");
        	nickName = body.get("nickname");
        	headImg = body.get("headimgurl");
        }
		
        UserWechat userWechat = new UserWechat();
        userWechat.setUserId(userId);
        userWechat.setOpenId(openId);
        userWechat.setUnionId(unionId == null ? null : String.valueOf(unionId));
        userWechat.setNickName(nickName == null ? null : String.valueOf(nickName));
        userWechat.setHeadImg(headImg == null ? null : String.valueOf(headImg));
        userWechat.setManualBind(WechatSopce.USER_INFO.equals(wechatSopce));
        
		return userWechat;
	}

	@Override
	public void bindUserWechat(UserWechat userWechat) {
		
		//查询openID 是否绑定
		UserWechat openIdDO = userWechatService.queryByOpenIdInCache(userWechat.getOpenId());
		if(!userWechat.getManualBind() && openIdDO != null && openIdDO.getManualBind()) {
			//当前操作为自动绑定，但openId已被手动绑定
            log.info("用户ID[{}],openId[{}],openId已被手动绑定,无法自动更新绑定关系。");
            return;
		}
		
		//查询该用户是否绑定
		UserWechat userIdDO = userWechatService.queryByUserIdInCache(userWechat.getUserId());
		if(!userWechat.getManualBind() && userIdDO != null && userIdDO.getManualBind()) {
			//当前操作为自动绑定，但userId已被手动绑定
            log.info("用户ID[{}],openId[{}],userId 已被手动绑定,无法自动更新绑定关系。");
            return;
		} 
		
		if(openIdDO != null && userIdDO != null && openIdDO.getId().equals(userIdDO.getId())) {
			//openIdDO、userIdDO 均已绑定，且同一个，则更新
			userWechat.setId(openIdDO.getId());
			userWechatService.updateUserWechat(userWechat);
			return;
		}
		
		if(openIdDO != null && userIdDO == null) {
			//openIdDO 绑定了，userIdDO 未绑定
			userWechat.setId(openIdDO.getId());
			userWechatService.updateUserWechat(userWechat);
			return;
		}
		
		if(userIdDO != null && openIdDO == null) {
			//userIdDO 绑定了，openIdDO 未绑定
			userWechat.setId(userIdDO.getId());
			userWechatService.updateUserWechat(userWechat);
			return;
		}
		
		//以上情况都不是，则新增用户-微信关联关系UserWechat
		userWechatService.inserUserWechat(userWechat);
	}

}
